package com.coocaa.typhoon.api.third.volcengine.service;

import cn.hutool.core.io.file.FileNameUtil;
import com.coocaa.cirrus.common.base.exception.BusinessException;
import com.coocaa.cirrus.common.base.model.MetaDataModel;
import com.coocaa.cirrus.common.base.util.MetaDataParseUtil;
import com.coocaa.cirrus.common.third.filestore.config.TosProperties;
import com.coocaa.typhoon.api.third.volcengine.model.video.VideoInfo;
import com.coocaa.typhoon.api.v1.userfile.model.UploadTmpResult;
import com.coocaa.typhoon.api.v1.userfile.model.output.RlsFileInfo;
import com.coocaa.typhoon.api.v1.userfile.model.output.TaskProgressResultModel;;
import com.coocaa.typhoon.api.v1.userfile.util.FileTypeCheckUtil;
import com.coocaa.typhoon.api.v1.userfile.util.TosFinalUrlUtil;
import com.coocaa.typhoon.api.v1.userfile.util.TosUploadFilePathUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.volcengine.tos.TOSV2;
import com.volcengine.tos.comm.HttpMethod;
import com.volcengine.tos.comm.event.DataTransferListener;
import com.volcengine.tos.comm.event.DataTransferStatus;
import com.volcengine.tos.comm.event.DataTransferType;
import com.volcengine.tos.internal.util.TosUtils;
import com.volcengine.tos.model.object.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Comparator;
import java.util.List;
import java.util.function.Consumer;

/**
 * @author bijiahao
 * @date : 2025/9/18 14:21
 * @description
 */
@Slf4j
@Service
public class TOSService {
    @Autowired
    private TOSV2 tosv2;
    @Autowired
    private TosProperties tosProperties;
    // 以 100MB 作为阈值，大于等于此阈值使用 uploadFile 接口上传，小于则使用 putObjectFromFile 上传
    // 此值仅供参考，请根据客户端资源情况进行调整
    private static long THRES_HOLD = 100 * 1024 * 1024;
    private static final String LOCAL_DIR = System.getenv("LOCAL_UPLOAD_DIR");

    private static final String STYLE_FOR_VIDEO_SNAPSHOT = "video/snapshot,t_{{time}},w_{{width}},h_{{height}},f_{{format}},m_fast";

    /**
     * 如果 w 和 h 都为 0，则输出原视频宽高。
     *
     * @param second
     * @return
     */
    private static String getStyleForVideoSnapshot(int second) {
        String time = String.valueOf(second * 1000);
        return STYLE_FOR_VIDEO_SNAPSHOT.replace("{{time}}", time)
                .replace("{{width}}", "0")
                .replace("{{height}}", "720")
                .replace("{{format}}", "png")
                ;
    }

    public UploadTmpResult videoSnapshot(String videoFilePath, int second, String remoteUploadFilePath, String extName) {
        String bucketName = tosProperties.getTosBucketName();
        ////视频截帧，截取第 多少ms 的视频帧
        String style = getStyleForVideoSnapshot(second);
        String saveBucket = tosProperties.getTosBucketName();
        GetObjectV2Input input = new GetObjectV2Input().setBucket(bucketName).setKey(videoFilePath).setProcess(style)
                .setSaveBucket(Base64.getUrlEncoder().encodeToString(saveBucket.getBytes(StandardCharsets.UTF_8)))
                .setSaveObject(Base64.getUrlEncoder().encodeToString(remoteUploadFilePath.getBytes(StandardCharsets.UTF_8)));
        GetObjectV2Output output = tosv2.getObject(input);
        UploadTmpResult tmpResult = UploadTmpResult.builder()
                .fileExtName(extName.toUpperCase())
                .fileName(FileNameUtil.getName(remoteUploadFilePath))
                .tosLink(TosFinalUrlUtil.getTosLink(tosProperties.getTosBucketName(), remoteUploadFilePath))
                .build();
        return tmpResult;
    }

    public VideoInfo getVideoInfo(String filePath) {
        String bucketName = tosProperties.getTosBucketName();
        // 获取视频信息
        String style = "video/info";
        GetObjectV2Input input = new GetObjectV2Input().setBucket(bucketName).setKey(filePath).setProcess(style);
        // 以下代码展示如何将视频信息转换为对应的类。
        try (ByteArrayOutputStream stream = new ByteArrayOutputStream(); GetObjectV2Output output = tosv2.getObject(input)) {
            byte[] buffer = new byte[1024];
            int length;
            while ((length = output.getContent().read(buffer)) != -1) {
                stream.write(buffer, 0, length);
            }
            String respBody = stream.toString("UTF-8");
            VideoInfo info = TosUtils.getJsonMapper().readValue(respBody, new TypeReference<VideoInfo>() {
            });
            return info;
        } catch (Exception e) {
            log.error("getVideoInfo error", e);
            return null;
        }

    }

    public UploadTmpResult uploadFile(File localSaveFile, String fileLocalPath, Consumer<TaskProgressResultModel> updateTaskProgress) {
        String ext = FileNameUtil.extName(fileLocalPath);
        String fileName = FileNameUtil.getName(fileLocalPath);
        String remoteFilePath = TosUploadFilePathUtil.getRemoteUploadFilePath(tosProperties.getTosEnvDir(), fileName);
        MetaDataModel metaDataModel = null;
        String tosLink = TosFinalUrlUtil.getTosLink(tosProperties.getTosBucketName(), remoteFilePath);
        String url = getFinalUrl(remoteFilePath);
        try {
            upload(localSaveFile, fileLocalPath, remoteFilePath, updateTaskProgress, tosLink);
            ext = FileTypeCheckUtil.getFileType(ext, localSaveFile);
            if (FileTypeCheckUtil.isImageExtension(ext)) {
                metaDataModel = MetaDataParseUtil.parseMetaData(localSaveFile);
            }
        } catch (Exception e) {
            log.error("文件上传异常:", e);
            throw new BusinessException("文件上传异常");
        }
        UploadTmpResult tmpResult = UploadTmpResult.builder()
                .fileExtName(ext)
                .fileName(fileName)
                .tosLink(tosLink)
                .url(url)
                .build();
        tmpResult.setMetaData(metaDataModel);
        return tmpResult;
    }

    /**
     * 最终的地址
     *
     * @param remoteFilePath
     * @return
     */
    public String getFinalUrl(String remoteFilePath) {
        return TosFinalUrlUtil.getHttpUrl(tosProperties.getTosUrlPrefix(), remoteFilePath);
//        return getSignTosHttpUrl(remoteFilePath);
    }


    public void uploadFile(ByteArrayOutputStream imgOut, String remoteUploadFilePath) {
        String bucketName = tosProperties.getTosBucketName();
        PutObjectInput putObjectInput = new PutObjectInput()
                .setBucket(bucketName).
                setKey(remoteUploadFilePath)
                .setContent(new ByteArrayInputStream(imgOut.toByteArray()))
                .setContentLength(imgOut.size());
        PutObjectOutput output = tosv2.putObject(putObjectInput);
    }


    public void upload(File outputFile, String fileLocalPath, String remoteUploadFilePath, Consumer<TaskProgressResultModel> updateTaskProgress, String tosLink) {
        if (outputFile.length() >= THRES_HOLD) {
            uploadLargeFile(fileLocalPath, remoteUploadFilePath, updateTaskProgress, tosLink);
        } else {
            uploadSmallFile(fileLocalPath, remoteUploadFilePath, updateTaskProgress, tosLink);
        }
    }

    private void uploadSmallFile(String fileLocalPath, String remoteUploadFilePath, Consumer<TaskProgressResultModel> updateTaskProgress, String tosLink) {
        try {
            String bucketName = tosProperties.getTosBucketName();
            PutObjectFromFileInput putObjectInput = new PutObjectFromFileInput().setBucket(bucketName).setKey(remoteUploadFilePath).setFilePath(fileLocalPath);
            putObjectInput.setDataTransferListener(getDataTransferListener(updateTaskProgress, tosLink));
            PutObjectFromFileOutput output = tosv2.putObjectFromFile(putObjectInput);
            log.info("uploadSmallFile succeed, 本地文件: {},远程文件: {} ", fileLocalPath, remoteUploadFilePath);
        } catch (Exception e) {
            log.error("uploadSmallFile error", e);
            throw new BusinessException("uploadFile error");
        }
    }

    private void uploadLargeFile(String fileLocalPath, String remoteUploadFilePath, Consumer<TaskProgressResultModel> updateTaskProgress, String tosLink) {
        // taskNum 设置并发上传的并发数，范围为 1-1000，默认为 1
        int taskNum = 5;
        // partSize 设置文件分片大小，范围为 5MB-5GB，默认为 20MB
        long partSize = 20 * 1024 * 1024;
        // enableCheckpoint 设置是否开启断点续传功能，开启则会在本地记录上传进度
        boolean enableCheckpoint = true;
        // checkpointFilePath 设置断点续传记录文件存放位置，若不设置则默认在 uploadFileDirectory 路径下生成
        // 其格式为 {uploadFileDirectory}.{bucket+objectKey 的 Base64Md5 值}.upload
        String bucketName = tosProperties.getTosBucketName();
        String checkpointFilePath = "";
        try {

            UploadFileV2Input input = new UploadFileV2Input().setBucket(bucketName).
                    setKey(remoteUploadFilePath)
                    .setFilePath(fileLocalPath).setEnableCheckpoint(enableCheckpoint)
                    .setCheckpointFile(checkpointFilePath).setPartSize(partSize).setTaskNum(taskNum);
            input.setDataTransferListener(getDataTransferListener(updateTaskProgress, tosLink));
            UploadFileV2Output output = tosv2.uploadFile(input);
            log.info("uploadLargeFile succeed, 本地文件: {},远程文件: {} ", fileLocalPath, remoteUploadFilePath);
        } catch (Exception e) {
            log.error("uploadLargeFile error", e);
            throw new BusinessException("uploadFile error");
        }
    }

    public List<RlsFileInfo> listRlsFileList(String parentFilePath) {
        List<String> filePathList = listRlsFilePath(parentFilePath);
        List<RlsFileInfo> res = new ArrayList<>();
        filePathList.forEach(filePath -> {
            RlsFileInfo rlsFileInfo = new RlsFileInfo();
            rlsFileInfo.setFilePath(filePath);
            rlsFileInfo.setFileUrl(getFinalUrl(filePath));
            rlsFileInfo.setFileType(FileNameUtil.extName(filePath).toUpperCase());
            res.add(rlsFileInfo);
        });
        return res;
    }

    public List<String> listRlsFilePath(String parentFilePath) {
        ListObjectsType2Input input = new ListObjectsType2Input();
        input.setBucket(tosProperties.getTosBucketName());
        String prefix = StringUtils.substringBeforeLast(parentFilePath, ".") + "_";
        input.setPrefix(prefix);
        String continuationToken = "";
        boolean truncated = true;
        ListObjectsType2Output result;
        List<String> res = new ArrayList<>();
        while (truncated) {
            input.setContinuationToken(continuationToken);
            result = tosv2.listObjectsType2(input);
            for (ListedObjectV2 listedObjectV2 : result.getContents()) {
                String key = listedObjectV2.getKey();
                res.add(key);
            }
            truncated = result.isTruncated();
            continuationToken = result.getNextContinuationToken();
        }
        //按页码排序
        res.sort(Comparator.comparingInt(TosFinalUrlUtil::extractNumber));
        return res;

    }

    public String getSignTosHttpUrl(String filePath) {
        //半天时间
        long expires = 60 * 60 * 12;
        PreSignedURLInput input = new PreSignedURLInput()
                .setBucket(tosProperties.getTosBucketName())
                .setKey(filePath)
                .setHttpMethod(HttpMethod.GET)
                // 过期时间(秒)
                .setExpires(expires);
        PreSignedURLOutput output = tosv2.preSignedURL(input);
        return output.getSignedUrl();
    }

    private static DataTransferListener getDataTransferListener(Consumer<TaskProgressResultModel> updateTaskProgress, String tosLink) {
        return new DataTransferListener() {
            // 自定义实现 DataTransferListener 的 dataTransferStatusChange 接口
            @Override
            public void dataTransferStatusChange(DataTransferStatus dataTransferStatus) {
                if (dataTransferStatus.getType() == DataTransferType.DATA_TRANSFER_STARTED) {
                    log.info("上传开始: ");
                } else if (dataTransferStatus.getType() == DataTransferType.DATA_TRANSFER_RW) {
                    doUpdateProgress(dataTransferStatus, updateTaskProgress, tosLink);

                } else if (dataTransferStatus.getType() == DataTransferType.DATA_TRANSFER_FAILED) {
                    doUpdateProgress(dataTransferStatus, updateTaskProgress, tosLink);

                } else if (dataTransferStatus.getType() == DataTransferType.DATA_TRANSFER_SUCCEED) {
                    doUpdateProgress(dataTransferStatus, updateTaskProgress, tosLink);
                }
            }
        };
    }

    private static void doUpdateProgress(DataTransferStatus dataTransferStatus, Consumer<TaskProgressResultModel> updateTaskProgress, String tosLink) {
        Float progress = getProgress(dataTransferStatus);
        TaskProgressResultModel progressResultModel = new TaskProgressResultModel(progress, dataTransferStatus.getType().toString());
        if (dataTransferStatus.getType() == DataTransferType.DATA_TRANSFER_SUCCEED) {
            progressResultModel.setTosLink(tosLink);
        }
        updateTaskProgress.accept(progressResultModel);
    }

    private static Float getProgress(DataTransferStatus dataTransferStatus) {
        Float progress = dataTransferStatus.getConsumedBytes() * 1.0f / dataTransferStatus.getTotalBytes();
        return progress;
    }

}
